package XML;

import org.dom4j.*;
import db.definitions.*;
import java.io.File;
import java.io.FilenameFilter;
import java.util.ArrayList;
import java.util.Iterator;
import org.dom4j.io.SAXReader;

/**
 * @author abracadabuda
 */
public class XmlLoader {

    private static final int TYPE_VIEW = 1;
    private static final int TYPE_TABLE = 2;
    private static final int TYPE_RULE = 3;
    private static final int TYPE_INIT = 4;
    private static final int TYPE_FUNCTION = 5;
    private static final int TYPE_TRIGGER = 6;
    private static final int TYPE_NONE = 0;
    private DatabaseDefinition database;

    /**
     * loads XML files from specified directory, parses XMLs into SQL code and returns the whole code for database on given schema
     * @param directoryName directory, where XMLs should be stored
     * @param schemaName schema name for database
     * @return SQL code for creating the datbase
     * @throws org.dom4j.DocumentException if problem with XML occured
     * @throws java.lang.Exception
     */
    public String loadXml(String directoryName, String schemaName) throws DocumentException, Exception {
        File dir = new File(directoryName);
        FilenameFilter filter = new FilenameFilter() {

            public boolean accept(File dir, String name) {
                return name.endsWith(".xml");
            }
        };
        String[] children = dir.list(filter); //loads all XML files

        ArrayList<String> fileNames = new ArrayList<String>();
        if (children == null) {
        } else {
            for (int i = 0; i < children.length; i++) {
                fileNames.add(directoryName + "/" + (String) children[i]); //join with directoryName
            }
        }

        database = new DatabaseDefinition(schemaName);
        loadDatabase(database, fileNames); //load database (all tables, triggers, views, functions, ...)

        return database.getSQL(); //return SQL code for database
    }

    /**
     * returns part of SQL code of creating feedback database by given type
     * @param type type of SQL code (view, table, ...)
     * @return SQL code
     */
    public String getSql(int type) {
        if (database != null) {
            return database.getSQL(type);
        } else {
            return "";
        }
    }

    /**
     * which type am i loading? :)
     * @param fileName
     * @return
     */
    private int getTypeOfFile(String fileName) {
        if (fileName.contains("table")) {
            return TYPE_TABLE;
        } else if (fileName.contains("view")) {
            return TYPE_VIEW;
        } else if (fileName.contains("rule")) {
            return TYPE_RULE;
        } else if (fileName.contains("init")) {
            return TYPE_INIT;
        } else if (fileName.contains("trigger")) {
            return TYPE_TRIGGER;
        } else if (fileName.contains("function")) {
            return TYPE_FUNCTION;
        }
        return TYPE_NONE;
    }

    /**
     * loads whole database into structure
     * @param database databaseDefinition
     * @param fileNames array of filename of XML files
     * @throws org.dom4j.DocumentException if problem with XML occured
     */
    private void loadDatabase(DatabaseDefinition database, ArrayList<String> fileNames) throws DocumentException {
        for (Iterator f = fileNames.iterator(); f.hasNext();) { //for all files
            String fileName = (String) f.next();
            SAXReader reader = new SAXReader();
            Document document = reader.read(fileName); //load XML structure
            Element root = document.getRootElement();

            switch (getTypeOfFile(fileName)) {
                case TYPE_TABLE:
                    for (Iterator i = root.elementIterator("table-def"); i.hasNext();) {
                        Element tableElement = (Element) i.next();
                        database.addTable(loadTable(tableElement, database));
                    }
                    break;
                case TYPE_VIEW:
                    for (Iterator i = root.elementIterator("view-def"); i.hasNext();) {
                        Element viewElement = (Element) i.next();
                        database.addView(loadView(viewElement, database));
                    }
                    break;
                case TYPE_RULE:
                    for (Iterator i = root.elementIterator("rule-def"); i.hasNext();) {
                        Element ruleElement = (Element) i.next();
                        database.addRule(loadRule(ruleElement));
                    }
                    break;
                case TYPE_INIT:
                    for (Iterator i = root.elementIterator("script-def"); i.hasNext();) {
                        Element scriptElement = (Element) i.next();
                        database.addScript(loadScript(scriptElement));
                    }
                    break;
                case TYPE_TRIGGER:
                    for (Iterator i = root.elementIterator("trigger-def"); i.hasNext();) {
                        Element triggerElement = (Element) i.next();
                        database.addTrigger(loadTrigger(triggerElement));
                    }
                    break;
                case TYPE_FUNCTION:
                    for (Iterator i = root.elementIterator("function-def"); i.hasNext();) {
                        Element functionElement = (Element) i.next();
                        database.addFunction(loadFunction(functionElement));
                    }

                default:
            }


        }
    //return database;
    }

    private TableDefinition loadTable(Element tableElement, DatabaseDefinition database) { //loads table
        TableDefinition table = new TableDefinition(database);
        table.setName(tableElement.attributeValue("name"));
        table.setName(tableElement.element("table-name").getTextTrim());
        table.setCatalog(tableElement.element("table-catalog").getTextTrim());
        //table.setSchema(tableElement.element("table-schema").getTextTrim());
        table.setTblName(tableElement.element("table-name").getTextTrim());
        table.setComment(tableElement.element("table-comment").getTextTrim());

        for (Iterator i = tableElement.elementIterator("column-def"); i.hasNext();) {
            Element columnElement = (Element) i.next();
            table.addColumn(loadColumn(columnElement, table));
        }

        for (Iterator i = tableElement.elementIterator("index-def"); i.hasNext();) {
            Element indexElement = (Element) i.next();
            table.addIndex(loadIndex(indexElement, table));
        }
        return table;
    }

    private ColumnDefinition loadColumn(Element columnElement, TableDefinition table) {
        ColumnDefinition column = new ColumnDefinition(table);
        column.setName(columnElement.attributeValue("name"));
        column.setPosition(Integer.parseInt(columnElement.element("dbms-position").getTextTrim()));
        column.setColName(columnElement.element("column-name").getTextTrim());
        column.setComment(columnElement.element("comment").getTextTrim());
        column.setDefaultValue(columnElement.element("default-value").getTextTrim());
        column.setPk(Boolean.parseBoolean(columnElement.element("primary-key").getTextTrim()));
        column.setNullable(Boolean.parseBoolean(columnElement.element("nullable").getTextTrim()));
        column.setType(columnElement.element("dbms-data-type").getTextTrim());

        for (Iterator i = columnElement.elementIterator("references"); i.hasNext();) {
            Element referenceElement = (Element) i.next();
            table.addReference(loadReference(referenceElement, column));
        }

        return column;
    }

    private ReferenceDefinition loadReference(Element referenceElement, ColumnDefinition column) {
        ReferenceDefinition reference = new ReferenceDefinition(column);
        reference.setCatalog(referenceElement.element("table-catalog").getTextTrim());
        //reference.setSchema(referenceElement.element("table-schema").getTextTrim());
        reference.setTblName(referenceElement.element("table-name").getTextTrim());
        reference.setColName(referenceElement.element("column-name").getTextTrim());
        reference.setName(referenceElement.element("constraint-name").getTextTrim());
        reference.setDeleteRule(referenceElement.element("delete-rule").getTextTrim());
        reference.setUpdateRule(referenceElement.element("update-rule").getTextTrim());
        reference.setDefferable(referenceElement.element("deferrable").getTextTrim());

        return reference;
    }

    private IndexDefinition loadIndex(Element indexElement, TableDefinition table) {
        IndexDefinition index = new IndexDefinition(table);
        index.setName(indexElement.element("name").getTextTrim());
        index.setExpression(indexElement.element("index-expression").getTextTrim());
        index.setUnique(Boolean.parseBoolean(indexElement.element("unique").getTextTrim()));
        index.setPk(Boolean.parseBoolean(indexElement.element("primary-key").getTextTrim()));
        index.setType(indexElement.element("type").getTextTrim());

        Element columnList = indexElement.element("column-list");
        for (Iterator i = columnList.elementIterator("column"); i.hasNext();) {
            Element column = (Element) i.next();
            index.addColumn(column.attributeValue("name"));
        }
        return index;

    }

    private ViewDefinition loadView(Element viewElement, DatabaseDefinition database) {
        ViewDefinition view = new ViewDefinition(database);
        view.setName(viewElement.attributeValue("name"));
        view.setCatalog(viewElement.element("view-catalog").getTextTrim());
        //view.setSchema(viewElement.element("view-schema").getTextTrim());
        view.setViewName(viewElement.element("view-name").getTextTrim());
        view.setComment(viewElement.element("view-comment").getTextTrim());
        view.setSource(viewElement.element("view-source").getTextTrim());
        return view;
    }

    private RuleDefinition loadRule(Element ruleElement) {
        RuleDefinition rule = new RuleDefinition();
        rule.setName(ruleElement.attributeValue("name"));
        rule.setCatalog(ruleElement.elementText("rule-catalog"));
        rule.setAction(ruleElement.elementText("rule-action"));
        //rule.setSchema(ruleElement.elementText("rule-schema"));
        rule.setSource(ruleElement.elementText("rule-source"));
        rule.setTable(ruleElement.elementText("rule-table"));
        rule.setType(ruleElement.elementText("rule-type"));
        rule.setWhere(ruleElement.elementText("rule-where"));
        return rule;
    }

    private String loadScript(Element scriptElement) {
        return scriptElement.getTextTrim();
    }

    private TriggerDefinition loadTrigger(Element triggerElement) {
        TriggerDefinition trigger = new TriggerDefinition();
        trigger.setName(triggerElement.attributeValue("name"));
        trigger.setEvent(triggerElement.elementTextTrim("trigger-event"));
        trigger.setProcedure(triggerElement.elementTextTrim("trigger-procedure"));
        //trigger.setSchema(triggerElement.elementTextTrim("trigger-schema"));
        trigger.setTable(triggerElement.elementTextTrim("trigger-table"));
        trigger.setTarget(triggerElement.elementTextTrim("trigger-target"));
        trigger.setTime(triggerElement.elementTextTrim("trigger-time"));
        return trigger;
    }

    private FunctionDefinition loadFunction(Element functionElement) {
        FunctionDefinition function = new FunctionDefinition();
        function.setName(functionElement.attributeValue("name"));
        function.setLang(functionElement.elementTextTrim("function-lang"));
        function.setBody(functionElement.elementTextTrim("function-body"));
        function.setParameters(functionElement.elementTextTrim("function-parameters"));
        function.setRest(functionElement.elementTextTrim("function-rest"));
        function.setReturnStat(functionElement.elementTextTrim("function-return"));
        return function;
    }
}
